#ifndef TEXTURE_H_
#define TEXTURE_H_

#include <string.h>
#include <string>
using namespace std;
#include "cglx.h"


/**
 * A class that represents a texture object for OpenGL.
 */
class Texture
{
private:
    GLuint myImageWidth;
    GLuint myImageHeight;
    GLuint myImageDepth;
    GLubyte* myImageData;
    GLuint myTarget;
    GLuint myID;

public:
    /**
     * Create an empty texture.
     */
    Texture ()
        : myImageWidth(0),
          myImageHeight(0),
          myImageDepth(3),
          myImageData(0),
          myTarget(GL_TEXTURE_2D),
          myID(0)
    {}

    /**
     * Create a texture from another texture.
     *
     * Note, copies data so that both can be manipulated separately.
     */
    Texture (const Texture& other)
        : myImageWidth(0),
          myImageHeight(0),
          myImageDepth(3),
          myImageData(0),
          myTarget(GL_TEXTURE_2D),
          myID(createTextureID())
    {
        updateData(other.getImageWidth(), other.getImageHeight(), other.getImageData());
    }

    /**
     * Create a texture from the given file.
     *
     * Currently only supports formats: jpg
     */
    Texture (const string& filename)
        : myImageWidth(0),
          myImageHeight(0),
          myImageDepth(3),
          myImageData(0),
          myTarget(GL_TEXTURE_2D),
          myID(createTextureID())
    {
        updateData(filename);
    }

    /**
     * Create a texture from the given data.
     *
     * Note, this texture is now responsible for deleting the data.
     */
    Texture (GLuint width, GLuint height, GLubyte* data)
        : myImageWidth(width),
          myImageHeight(height),
          myImageDepth(3),
          myImageData(data),
          myTarget(GL_TEXTURE_2D),
          myID(createTextureID())
    {
        bindData();
    }

    /**
     * Free texture's resources.
     */
    ~Texture ()
    {
        if (myImageData != 0)
        {
            dispose();
            delete myImageData;
        }
    }


    /**
     * Allow assignment between textures.
     */
    Texture& operator= (const Texture & other)
    {
        // do not copy unless you have to
        if (this != &other)
        {
            updateData(other.getImageWidth(), other.getImageHeight(), other.getImageData());
        }
        return *this;
    }


    /**
     * Enables texture's target (e.g., GL_TEXTURE_2D).
     */
    void enable () const
    {
        glEnable(myTarget);
    }

    /**
     * Disables texture's target (e.g., GL_TEXTURE_2D).
     */
    void disable () const
    {
        glDisable(myTarget);
    }

    /**
     * Binds this texture to the current GL context.
     */
    void bind () const
    {
        glBindTexture(myTarget, myID);
    }

    /**
     * Disposes the native resources used by this texture object.
     */
    void dispose ()
    {
        glDeleteTextures(1, &myID);
        myID = 0;
    }

    /**
     * Returns the width of the image contained within this texture.
     */
    GLuint getImageWidth () const
    {
        return myImageWidth;
    }

    /**
     * Returns the height of the image contained within this texture.
     */
    GLuint getImageHeight () const
    {
        return myImageHeight;
    }

    /**
     * Returns original aspect ratio of the image.
     */
    GLuint getImageDepth () const
    {
        return myImageDepth;
    }

    /**
     * Returns original aspect ratio of the image.
     */
    GLfloat getAspectRatio () const
    {
        return GLfloat(myImageWidth) / myImageHeight;
    }

    /**
     * Returns the width of the image contained within this texture.
     */
    GLubyte* getImageData () const
    {
        return myImageData;
    }


    /**
     * Change this texture's underlying data.
     */
    void updateData (const string& filename)
    {
        delete myImageData;
        // width and height set by reference
        myImageData = readFile(filename, myImageWidth, myImageHeight);
        bindData();
    }

    /**
     * Change this texture's underlying data to the given data.
     *
     * Note, this texture is now responsible for deleting the data.
     */
    void updateData (GLuint width, GLuint height, GLubyte* data)
    {
        delete myImageData;
        myImageWidth = width;
        myImageHeight = height;
        myImageData = new GLubyte[myImageWidth * myImageHeight * myImageDepth];
        memcpy(myImageData, data, myImageWidth * myImageHeight * myImageDepth);
        bindData();
    }


private:
    // associate this texture's id with its pixel data
    //void bindData (GLuint width, GLuint height, GLubyte* data)
    void bindData ()
    {
        //printf("ID = %d\n", myID);
        glBindTexture(GL_TEXTURE_2D, myID);
        // set standard settings for this ID
        // defines how to store the pixel
        //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // stores texture information for this ID
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                     myImageWidth, myImageHeight,
                     0, GL_RGB, GL_UNSIGNED_BYTE,
                     myImageData);
    }

    // Creates a new texture ID.
    static int createTextureID ()
    {
        GLuint result;
        glGenTextures(1, &result);
        return result;
    }

    // Determines type of image file and reads that specific format.
    static GLubyte* readFile (const string& filename, GLuint& width, GLuint& height);
};


#endif /* TEXTURE_H_ */
